/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       default_heroes.cpp

	$Header: /game/default_heroes.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "default_heroes.h"

#include <climits>
#include <algorithm>
#include <assert.h>
#include <vector>
#include "elements_of.h"
#include "hero_class.h"
#include "hero_class_properties.h"
#include "hero_portrait.h"
#include "streambuf_operators.h"
#include "table.h"
#include "table_ptr.h"

namespace
{
	struct t_hero_biography
	{
		std::string     keyword;
		int             portrait_id;
		std::string     name;
		bool            is_male;
		bool            is_generic;
		t_hero_class    hero_class;
		std::string     biography;
		bool			allowed_by_default;
	};

	typedef std::vector< t_hero_biography > t_hero_biography_vector;

	t_hero_biography_vector g_default_heroes;
	t_table_cache			g_hero_table( "heroes" );
	std::vector< int >		g_portrait_to_biography_id_table;

	struct t_class_keyword
	{
		t_hero_class hero_class;
		char const*  keyword;
	};

	struct t_find_class
	{
		bool operator()( t_class_keyword const& keyword, std::string const& name ) const
		{
			return stricmp(keyword.keyword, name.c_str()) < 0;
		}
	};

	class t_sort_biography
	{
	protected:
		int const* m_count;
	public:
		t_sort_biography( int const* count ) : m_count( count )
		{
		}

		bool operator()( int left, int right ) const
		{
			if (m_count[left] != m_count[right])
				return m_count[left] < m_count[right];
			return left < right;
		}
	};

	enum {
		k_column_keyword,
		k_column_name,
		k_column_sex,
		k_column_class,
		k_column_biography,
		k_column_allowed_by_default
	};

	// ---------------------------------------------------------
	// read table with default hero information
	// ---------------------------------------------------------
	void read_table_helper()
	{
		t_table_ptr						table = g_hero_table.get();
		t_table::iterator				line = table->begin();
		t_hero_biography				hero;
		std::vector< t_hero_biography >	new_default_heroes;
		std::vector< int >              new_portrait_to_biography_id_table( get_hero_portrait_count(), -1 );

		line++;
		for (; line != table->end(); line++)
		{
			if ((*line)[0].empty())
				continue;

			hero.keyword		= (*line)[k_column_keyword];
			hero.portrait_id	= find_hero_portrait( hero.keyword );
			hero.name			= (*line)[k_column_name];
			hero.is_male		= stricmp((*line)[k_column_sex].c_str(), "male" ) == 0;
			hero.is_generic     = stricmp( (*line)[k_column_class].c_str(), "generic" ) == 0;
			hero.hero_class     = get_hero_class( (*line)[k_column_class] );
			hero.biography      = (*line)[k_column_biography];
			
			int i;
			std::string allowed_by_default = (*line)[k_column_allowed_by_default];
			for ( i = 0; i < (int)allowed_by_default.size() && isspace(allowed_by_default[i]); i++)
        ;
			
			hero.allowed_by_default = ( i < (int)allowed_by_default.size() );

			if ( hero.portrait_id >= 0 )
			{
				assert( hero.portrait_id < (int)new_portrait_to_biography_id_table.size() );
				assert( new_portrait_to_biography_id_table[ hero.portrait_id ] == -1 );

				// new_default_heroes.size() is the biography id
				new_portrait_to_biography_id_table[ hero.portrait_id ] = new_default_heroes.size();
			}

			new_default_heroes.push_back( hero );
		}

		// Use assignment here to trim any excess space in the vector
		g_default_heroes = new_default_heroes;

		// There is no excess space in the new_portrait_to_biography_id_table
		// so use the more efficient swap().
		g_portrait_to_biography_id_table.swap( new_portrait_to_biography_id_table );
	}

	// ---------------------------------------------------------
	// read table with default hero information
	// ---------------------------------------------------------
	inline void read_table()
	{
		static bool initialized = false;
		if ( !initialized )
		{
			read_table_helper();
			initialized = true;
		}
	}

}; // unnamed namespace

// ---------------------------------------------------------
// object to record which portraits have been used
// ---------------------------------------------------------
t_default_hero_list::t_default_hero_list()
{
	read_table();

	int					    portrait_count = 0;
	t_hero_biography const*	bio;
	int                     i;

	// find the minimum # of times a portrait has been used
	for ( i = 0; i < (int)g_default_heroes.size(); i++)
	{
		bio = &g_default_heroes[i];

		if (bio->portrait_id > portrait_count)
			portrait_count = bio->portrait_id;
	}
	m_portrait_count.insert( m_portrait_count.end(), portrait_count + 1, 0 );
	m_biography_count.insert( m_biography_count.end(), g_default_heroes.size(), 0 );
}

// ---------------------------------------------------------
// object to record which portraits have been used
// return a hero with the specified portrait_id but a
// generic name and biography
// ---------------------------------------------------------
t_default_hero t_default_hero_list::get_generic( int portrait_id ) const
{
	assert( portrait_id >= 0 && portrait_id < (int)m_portrait_count.size() );

	int default_biography_id = g_portrait_to_biography_id_table[ portrait_id ];
	t_hero_biography const & default_hero = g_default_heroes[ default_biography_id ];

	t_hero_class hero_class = default_hero.hero_class;
	bool male = default_hero.is_male;

	int min_count = INT_MAX;

	int default_hero_count = g_default_heroes.size();

	int rarest_biography_id;
	int biography_id;
	for ( biography_id = 0; biography_id < default_hero_count; ++biography_id )
	{
		t_hero_biography const & hero = g_default_heroes[ biography_id ];

		if ( hero.is_male != male || !hero.is_generic )
			continue;

		int biography_count = m_biography_count[ biography_id ];
		if ( biography_count < min_count )
		{
			min_count = biography_count;
			rarest_biography_id = biography_id;
		}
	}

	t_hero_biography const & rarest_hero = g_default_heroes[ rarest_biography_id ];
	assert( rarest_hero.is_male == male && rarest_hero.is_generic );

	t_default_hero result;
	result.biography = rarest_hero.biography;
	result.biography_id = rarest_biography_id;
	result.hero_class = hero_class;
	result.is_male = male;
	result.name = rarest_hero.name;
	result.portrait_id = portrait_id;

	return result;
}

std::vector<t_default_hero> t_default_hero_list::get_unused( t_hero_class hero_class, 
															 bool male ) const
{
	// build list of unused portraits
	std::vector<t_default_hero> result;
	t_default_hero              hero;
	int							maximum_count = INT_MAX;
	int							i;
	t_hero_biography const*		bio;

	// find the minimum # of times a portrait has been used
	for ( i = 0; i < (int)g_default_heroes.size(); i++)
	{
		bio = &g_default_heroes[i];

		if (bio->hero_class != hero_class || bio->is_male != male || bio->is_generic)
			continue;
		if (bio->portrait_id < 0)
			continue;
		if (bio->portrait_id < (int)m_allowed_portraits.size() && !m_allowed_portraits[bio->portrait_id])
			continue;

		if (m_portrait_count[bio->portrait_id] < maximum_count)
			maximum_count = m_portrait_count[bio->portrait_id];
	}

	// first, use the normal, non-generic portrait / name / biography sets

	for ( i = 0; i < (int)g_default_heroes.size(); i++)
	{
		bio = &g_default_heroes[i];

		if (bio->hero_class != hero_class || bio->is_male != male || bio->is_generic)
			continue;

		if (bio->portrait_id < 0)
			continue;

		if (bio->portrait_id < (int)m_allowed_portraits.size() && !m_allowed_portraits[bio->portrait_id])
			continue;

		if (m_portrait_count[bio->portrait_id] > maximum_count)
			continue;

		hero.portrait_id  = bio->portrait_id;
		hero.hero_class   = bio->hero_class;
		hero.is_male      = bio->is_male;
		hero.biography_id = i;
		hero.name		  = bio->name;
		hero.biography	  = bio->biography;
		result.push_back( hero );
	}
	if (maximum_count == 0)
	{
		assert( !result.empty() );
		return result;
	}

	// all of the standard biographies are exhausted, attach generic biographies to
	// the standard portraits.
	std::vector<int> index;

	for ( i = 0; i < (int)g_default_heroes.size(); i++)
	{
		bio = &g_default_heroes[i];

		if (bio->is_male != male || !bio->is_generic)
			continue;

		if (bio->portrait_id >= 0 && bio->portrait_id < m_allowed_portraits.size() && !m_allowed_portraits[bio->portrait_id])
			continue;

		index.push_back( i );
	}

	// sort index by count, and then by id #
	std::sort( index.begin(), index.end(), t_sort_biography( &m_biography_count[ 0 ] ));

	// attach biographies.
	int j = 0;

	for ( i = 0; i < (int)result.size(); i++)
	{
		bio = &g_default_heroes[index[j]];

		// set the result's biography to the generic one.
		result[i].biography_id = index[j];
		result[i].name		 = bio->name;
		result[i].biography	 = bio->biography;
		// increment which biography we're using, and wrap around if we reach the end.
		if (++j == index.size())
			j = 0;
	}
	assert( !result.empty() );
	return result;
}

// ---------------------------------------------------------
// default hero list
// write to a file
// ---------------------------------------------------------
bool t_default_hero_list::write( std::streambuf& stream ) const
{
	put<t_uint16>( stream, m_portrait_count.size() );
	::write( stream, &(*m_portrait_count.begin()), m_portrait_count.size() * sizeof(int));
	put<t_uint16>( stream, m_biography_count.size() );
	::write( stream, &(*m_biography_count.begin()), m_biography_count.size() * sizeof(int));
	return true;
}

// ---------------------------------------------------------
// default hero list
// write to a file
// ---------------------------------------------------------
bool t_default_hero_list::read( std::streambuf& stream ) 
{
	int count;

	count = get<t_uint16>( stream );
	m_portrait_count.resize( count );
	::read( stream, &(*m_portrait_count.begin()), count * sizeof(int));
	count = get<t_uint16>( stream );
	m_biography_count.resize( count );
	::read( stream, &(*m_biography_count.begin()), count * sizeof(int));
	return true;
}

// ---------------------------------------------------------
// Get the default biography for a given biography id
// ---------------------------------------------------------
std::string get_default_biography( int biography_id )
{
	read_table();
	assert( biography_id >= 0 && biography_id < (int)g_default_heroes.size() );
	return g_default_heroes[ biography_id ].biography;
}

// ---------------------------------------------------------
// Map the given portrait_id to its default biography id
// ---------------------------------------------------------
int get_default_biography_id_from_portrait_id( int portrait_id )
{
	read_table();
	assert( portrait_id >= 0 && portrait_id < (int)g_portrait_to_biography_id_table.size() );
	return g_portrait_to_biography_id_table[ portrait_id ];
}

// ---------------------------------------------------------
// Get the default hero for a given biography id
// ---------------------------------------------------------
t_default_hero get_default_hero( int biography_id )
{
	read_table();
	assert( biography_id >= 0 && biography_id < (int)g_default_heroes.size() );

	t_hero_biography const & hero = g_default_heroes[ biography_id ];

	t_default_hero result;
	result.biography = hero.biography;
	result.biography_id = biography_id;
	result.hero_class = hero.hero_class;
	result.is_male = hero.is_male;
	result.name = hero.name;
	result.portrait_id = hero.portrait_id;

	return result;
}

// ---------------------------------------------------------
// Get the default hero class for a given biography id
// ---------------------------------------------------------
t_hero_class get_default_hero_class( int biography_id )
{
	read_table();
	assert( biography_id >= 0 && biography_id < (int)g_default_heroes.size() );
	return g_default_heroes[ biography_id ].hero_class;
}

// ---------------------------------------------------------
// Get the default gender for a given biography id
// ---------------------------------------------------------
bool get_default_is_male( int biography_id )
{
	read_table();
	assert( biography_id >= 0 && biography_id < (int)g_default_heroes.size() );
	return g_default_heroes[ biography_id ].is_male;
}

// ---------------------------------------------------------
// Get the default name for a given biography id
// ---------------------------------------------------------
std::string get_default_name( int biography_id )
{
	read_table();
	assert( biography_id >= 0 && biography_id < (int)g_default_heroes.size() );
	return g_default_heroes[ biography_id ].name;
}

// ---------------------------------------------------------
// Get the default portrait id for a given biography id
// ---------------------------------------------------------
int get_default_portrait_id( int biography_id )
{
	read_table();
	assert( biography_id >= 0 && biography_id < (int)g_default_heroes.size() );
	return g_default_heroes[ biography_id ].portrait_id;
}

// ---------------------------------------------------------
// Is this hero normally allowed in taverns?
// ---------------------------------------------------------
bool hero_allowed_by_default( int portrait_id )
{
	read_table();

	int biography_id = get_default_biography_id_from_portrait_id( portrait_id );
	
	if ( biography_id >= 0 && biography_id < (int)g_default_heroes.size() )
		return g_default_heroes[ biography_id ].allowed_by_default;
	else
		return false;
}
